package com.youlin.untils;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlin.mybean.ResMapping;
import com.youlin.mybean.SetSubList;
import com.youlin.mybean.SetSubProp;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class BaseService {

    private BaseService() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 查询并处理一对一关系 (新版，配合@ResMapping和@SetSubProp)
     */
    public static <K, P, PR, S> List<PR> findAndDealWithOneToOne(List<P> parentList, Function<P, K> getParentKey,
                                        Class<PR> parentResClass, IService<S> iService, SFunction<S, K> getsubKey) {
        List<K> ids = parentList.parallelStream().map(getParentKey).collect(Collectors.toList());
        if (ids.size() == 0) return parentList.parallelStream()
                .map(p -> resMapping(p, parentResClass)).collect(Collectors.toList());

        List<S> subList = iService.list(Wrappers.lambdaQuery(getEntityClass(iService)).in(getsubKey, ids));
        return dealWithOneToOne(parentList, getParentKey, parentResClass, subList, getsubKey);
    }

    /**
     * 处理一对一关系 (新版，配合@ResMapping和@SetSubProp)
     */
    public static <K, P, PR, S> List<PR> dealWithOneToOne(List<P> parentList, Function<P, K> getParentKey,
                                        Class<PR> parentResClass, List<S> subList, Function<S, K> getsubKey) {
        Map<K, S> subMap = subList.parallelStream().collect(Collectors.toMap(getsubKey, Function.identity()));
        return parentSetSub(parentList, getParentKey, parentResClass, subMap);
    }

    /**
     * 处理一对多关系：子类维护关联关系 (新版，配合@ResMapping和@SetSubProp)
     */
    public static <K, P, PR, S> List<PR> dealWithOneToMany(List<P> parentList, Function<P, K> getParentKey,
                                        Class<PR> parentResClass, List<S> subList, Function<S, K> getsubKey) {
        Map<K, List<S>> subMap = subList.parallelStream().collect(Collectors.groupingBy(getsubKey, Collectors.toList()));
        return parentSetSub(parentList, getParentKey, parentResClass, subMap);
    }

    /**
     * 将父类映射为DTO并设置子类DTO属性 (新版，配合@ResMapping和@SetSubProp)
     */
    private static <P, PK, PR, S> List<PR> parentSetSub (List<P> parentList, Function<P, PK> getParentKey,
                                                         Class<PR> parentResClass, Map<PK, S> subResMap) {
        List<PR> parentResList = parentList.parallelStream().map(parent -> {
            PK pk = getParentKey.apply(parent);
            return SetSubProp(resMapping(parent, parentResClass), subResMap.get(pk));
        }).collect(Collectors.toList());
        return parentResList;
    }

    /**
     * 将实体类映射为DTO (新版，配合@ResMapping，DTO可自定义字段名)
     */
    private static <PR, P> PR resMapping(P p, Class<PR> prClass) {
        try {
            if (p.getClass() == prClass) return (PR) p;
            PR pr = prClass.newInstance();
            ResMapping classMapping = prClass.getAnnotation(ResMapping.class);
            if (classMapping != null && classMapping.mappingClass().equals(p.getClass().getName())) {
                BeanUtils.copyProperties(p, pr);
                Arrays.asList(prClass.getDeclaredFields()).parallelStream()
                        .filter(prField -> {
                            ResMapping propMapping = prField.getAnnotation(ResMapping.class);
                            if (propMapping == null) return false;
                            return StringUtils.isNotBlank(propMapping.mappingProp());
                        }).forEach(prField -> {
                    try {
                        ResMapping propMapping = prField.getAnnotation(ResMapping.class);
                        String mappingPropName = propMapping.mappingProp();
                        Field pField = p.getClass().getDeclaredField(mappingPropName);
                        pField.setAccessible(true);
                        prField.setAccessible(true);
                        if (prField.get(pr) == null)
                            prField.set(pr, pField.get(p));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
            return pr;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 在父类DTO中设置子类DTO属性 (新版，配合@ResMapping和@SetSubProp)
     */
    private static <PR, S> PR SetSubProp(PR pr, S sub) {
        if (pr == null || sub == null) return pr;

        Class<?> subClass = sub.getClass();
        if (sub instanceof List) {
            if (CollectionUtils.isEmpty((List<?>) sub)) return pr;
            subClass = ((List<?>) sub).get(0).getClass();
        }
        Field[] prFields = pr.getClass().getDeclaredFields();
        Class<?> finalSubClass = subClass;

        Arrays.asList(prFields).parallelStream().filter(prField -> {
            SetSubList setSubList = prField.getAnnotation(SetSubList.class);
            if (setSubList == null) return false;
            return setSubList.subClass().equals(finalSubClass.getName());
        }).forEach(prField -> {
            try {
                SetSubList setSubList = prField.getAnnotation(SetSubList.class);
                Class<?> sc = Class.forName(setSubList.subClass());
                List<?> subResList = ((List<?>) sub).parallelStream().map(s -> resMapping(s, sc)).collect(Collectors.toList());
                prField.setAccessible(true);
                prField.set(pr, subResList);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        Arrays.asList(prFields).parallelStream().filter(prField -> {
            SetSubProp setSubProp = prField.getAnnotation(SetSubProp.class);
            if (setSubProp == null) return false;
            return setSubProp.subClass().equals(finalSubClass.getName());
        }).forEach(prField -> {
            try {
                SetSubProp setSubProp = prField.getAnnotation(SetSubProp.class);
                String primaryKeyName = setSubProp.primaryKey();
                String subPrimaryKeyName = setSubProp.subPrimaryKey();
                if (StringUtils.isNotBlank(primaryKeyName) && StringUtils.isNotBlank(subPrimaryKeyName)) {
                    Field primaryKeyField = pr.getClass().getDeclaredField(primaryKeyName);
                    primaryKeyField.setAccessible(true);
                    Object primaryKey = primaryKeyField.get(pr);
                    if (primaryKey == null) return;
                    Field subPrimaryKeyField = finalSubClass.getDeclaredField(subPrimaryKeyName);
                    subPrimaryKeyField.setAccessible(true);
                    Object subPrimaryKey = subPrimaryKeyField.get(sub);
                    if (! primaryKey.equals(subPrimaryKey)) return;
                }
                String subPropName = setSubProp.subProp();
                Field subField = finalSubClass.getDeclaredField(subPropName);
                subField.setAccessible(true);
                prField.setAccessible(true);
                if (prField.get(pr) == null)
                    prField.set(pr, subField.get(sub));
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        Arrays.asList(prFields).parallelStream().filter(prField -> {
            if (prField.getType() == finalSubClass) return true;
            ResMapping resMapping = prField.getAnnotation(ResMapping.class);
            if (resMapping == null) return false;
            return resMapping.mappingClass().equals(finalSubClass.getName());
        }).forEach(prField -> {
            try {
                prField.setAccessible(true);
                Object subRes = resMapping(sub, prField.getType());
                prField.set(pr, subRes);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        return pr;
    }

    /**
     * 获取 IService 中关联的实体类 Class
     */
    private static <S> Class<S> getEntityClass(IService<S> iService) {
        Class<?> implClass = (Class<?>) iService.getClass().getGenericSuperclass();
        ParameterizedType serviceType = (ParameterizedType) implClass.getGenericSuperclass();
        return (Class<S>) serviceType.getActualTypeArguments()[1];
    }

    public static <E, R> R copyProperties(E instance, Class<R> resClass) {
        try {
            if (instance.getClass() != resClass) {
                R subResData = resClass.newInstance();
                BeanUtils.copyProperties(instance, subResData);
                return subResData;
            } else return (R) instance;
        } catch (Exception e) {
            return null;
        }
    }

    public static <E> Integer getSitId(Class<E> eClass, SFunction<E, String> column, SFunction<E, String> eqSupplier,
                                       String eqValue, IService<E> iService, Map<Integer, Integer> sitMap) {
        LambdaQueryWrapper<E> sitWrapper = Wrappers.lambdaQuery(eClass)
                .select(column).isNotNull(column).eq(eqSupplier, eqValue);
        List<E> eList = iService.list(sitWrapper);
        if (eList.size() > 0) {
            Map<String, Long> siMap = eList.parallelStream().map(column)
                    .flatMap(sitIdStr -> Arrays.asList(sitIdStr.split(",")).parallelStream())
                    .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
            Optional<String> first = siMap.keySet().parallelStream().sorted((k1, k2) -> {
                if (siMap.get(k1).equals(siMap.get(k2)))
                    return sitMap.get(Integer.valueOf(k1)) - sitMap.get(Integer.valueOf(k2));
                else return (int) (siMap.get(k2) - siMap.get(k1));
            }).findFirst();
            if (first.isPresent()) return Integer.valueOf(first.get());
        }
        return null;
    }

}
